昨天介紹了 Median Filter。
到目前為止,我們學了三種不同的濾波方法。
它們的共同目標都是一樣的:
讓圖片變得更平滑,降低雜訊。
但如果今天的目的不是去除雜訊,而是想找出圖片中的物體輪廓,那又該怎麼做?
例如有一張圖片,裡面有一隻狗、一片草地,還有背景。
如果要請電腦指出狗的邊界,它該從哪裡開始?
事實上,電腦並不會直接知道哪裡是狗。
它真正找的是 亮度變化最大的地方。
這就是今天要介紹的 Image Gradient(影像梯度)。
「Gradient」這個詞,原本是數學裡的概念。
第一次看到時,還以為要重回微積分的惡夢了。

後來才發現,在影像處理裡,可以先把它理解成一件很簡單的事情。
亮度改變得有多快。
如果相鄰 Pixel 的亮度幾乎一樣。
120 121 122 121 120
代表這個地方變化很小,Gradient 也很小。
但是如果亮度突然改變。
20 22 21 210 215
可以發現,中間突然從 21 跳到 210。
這裡的亮度變化非常大,Gradient 也會很大。
而這種劇烈變化的地方,很可能就是物體的邊界。
想像一張黑白圖片。
左邊是黑色,右邊是白色。
0 0 0 0 255 255 255
在黑色區域,每個 Pixel 都差不多,Gradient 很小。
在白色區域,Gradient 一樣很小。
真正有變化的地方只有黑色和白色交界的位置。
因此只要找到 Gradient 很大的地方,通常就能找到圖片中的邊緣。
既然 Gradient 是亮度的變化。
最直接的方法,就是比較相鄰 Pixel。
例如:
20 22 21 24 23
相鄰 Pixel 的差異都很小。
2
-1
3
-1
但如果變成:
20 22 21 200 205
差值就會變成:
2
-1
179
5
可以看到,179 明顯比其他地方大得多。
因此這個位置,就是我們最關心的地方。
這就是 Gradient 最基本的概念。
之前介紹 Convolution 時有提到,只要換一個 Kernel,就能得到不同的效果。
但其實,Gradient 也是利用 Convolution 完成的。
只不過這次的 Kernel 不再是拿來平滑圖片,而是拿來計算亮度變化。
明天要介紹的 Sobel Operator 就是最經典的 Gradient Kernel。
OpenCV 已經提供了 Gradient 的相關函式。
例如使用 Sobel。
import cv2
import matplotlib.pyplot as plt
img = cv2.imread("dog.jpeg", cv2.IMREAD_GRAYSCALE)
grad_x = cv2.Sobel(img, cv2.CV_64F, 1, 0)
grad_y = cv2.Sobel(img, cv2.CV_64F, 0, 1)
接著把結果顯示出來。
plt.figure(figsize=(12,4))
plt.subplot(1,3,1)
plt.imshow(img, cmap="gray")
plt.title("Original")
plt.axis("off")
plt.subplot(1,3,2)
plt.imshow(grad_x, cmap="gray")
plt.title("Gradient X")
plt.axis("off")
plt.subplot(1,3,3)
plt.imshow(grad_y, cmap="gray")
plt.title("Gradient Y")
plt.axis("off")
plt.tight_layout()
plt.show()
看到結果時可能會發現畫面並不是原本的圖片。
而是只有輪廓附近比較亮,其他地方幾乎都是黑色。
原因是因為這張圖顯示的不是亮度,而是亮度變化的大小。
變化越大的地方,就越亮。
Image Gradient 是許多影像處理演算法的重要基礎。
例如:
後面會介紹的 Sobel、Prewitt、Laplacian,甚至 Canny Edge Detection,都和 Gradient 有密切的關係。
可以說,如果沒有 Gradient,很多經典的邊緣偵測方法都無法實現。
從今天開始,我們正式從之前的「平滑圖片」進入「分析圖片」。
Image Gradient 的核心概念並不複雜,它只關心 哪裡的亮度變化最大?
這些亮度變化劇烈的地方,往往就是物體的邊界。
明天,我們就會介紹最經典的 Gradient 演算法之一:
Sobel Operator(Sobel 邊緣偵測)。
來看看它是如何利用不同方向的 Kernel 找出圖片中的邊緣。